01 毛笔字效果 发表于 2020-01-01 | 分类于 Custom View | 无意中在 GitHub 上找到一个毛笔字效果的项目 canvas_WritingBrush 。其效果如下图所示: 网页源码精简为:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"> <title> canvas 递进</title> <style type="text/css"> #canvasId { background-color: #f2e0ba; } </style> </head> <body> <canvas id="canvasId"></canvas><br /> <input type="button" value="clear" onclick="hw.clear();" /> <img id="pen22" src="pen22.png"/><img id="pen2" src="pen2.png"/><!-- <script type="text/javascript" src="jQuery.min.js"></script> --><script type="text/javascript"> var l = 20; var arr = [];function Handwriting(id) { this.canvas = document.getElementById(id); this.ctx = this.canvas.getContext("2d"); this.canvas.setAttribute('width',window.screen.width * 0.9); this.canvas.setAttribute('height',window.screen.height *0.8); this.ctx.fillStyle = "rgba(0,0,0,0.8)"; var _this = this; this.canvas.addEventListener("touchstart", this.downEvent.bind(this), false); this.canvas.addEventListener("touchmove", this.moveEvent.bind(this), false); this.canvas.addEventListener("touchend", this.upEvent.bind(this), false); this.canvas.addEventListener("contextmenu", function(e){ e.preventDefault() }, false); // this.canvas.onmousedown = function (e) { _this.downEvent(e)}; // this.canvas.onmousemove = function (e) { _this.moveEvent(e)}; // this.canvas.onmouseup = function (e) { _this.upEvent(e)}; // this.canvas.onmouseout = function (e) { _this.upEvent(e)}; this.canvas.addEventListener("mousedown",function(e){ _this.downEvent(e)},false); this.canvas.addEventListener("mousemove",function(e){ _this.moveEvent(e)},false); this.canvas.addEventListener("mouseup",function(e){ _this.upEvent(e)},false); this.canvas.addEventListener("mouseout",function(e){ _this.upEvent(e)},false); this.canvas.addEventListener("click",function(e){ _this.clickEvent(e),false}); this.moveFlag = false; this.upof = {}; this.has = []; this.linePressure = 1; this.smoothness = 80; this.img = document.getElementById('pen2'); this.img1 = document.getElementById('pen22');} Handwriting.prototype.clear = function () { this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height); } Handwriting.prototype.clickEvent = function(e){ this.cli = this.getXY(e); // this.ctx.drawImage(this.img,this.cli.x - l/2,this.cli.y - l/2,l,l);} Handwriting.prototype.downEvent = function (e) { this.moveFlag = true; this.has = []; this.upof = this.getXY(e); // this.ctx.drawImage(this.img,(this.upof.x - this.big/2),(this.upof.y - this.big/2),this.big,this.big); var x1 = this.upof.x; var y1 = this.upof.y; arr.unshift({x1,y1});} Handwriting.prototype.moveEvent = function (e) { if (!this.moveFlag) return; e.preventDefault(); var of = this.getXY(e); //move var up = this.upof; //down this.has.unshift({time:new Date().getTime() ,dis:this.distance(up,of)}); var dis = 0; var time = 0; for (var n = 0; n < this.has.length-1; n++) { dis += this.has[n].dis; time += this.has[n].time-this.has[n+1].time; if (dis>this.smoothness) break; } this.upof = of; var len = Math.round(this.has[0].dis/2)+1; for (var i = 0; i < len; i++) { var x = up.x + (of.x-up.x)/len*i; var y = up.y + (of.y-up.y)/len*i; // var r = ur + (or-ur)/len*i; this.ctx.beginPath(); // this.ctx.arc(x,y,r,0.2*Math.PI,1.5*Math.PI,true); // this.ctx.fill(); // var r_r = r*2; x = x-l/2; y = y - l/2; arr.unshift({x,y}); this.ctx.drawImage(this.img,x,y,l,l); l = l - 0.2; if( l < 10) l = 10; // console.log(l); } } Handwriting.prototype.upEvent = function (e) { this.moveFlag = false; // console.log(p); l = 20; // console.log(arr); if(arr.length >100){ for(var j = 0; j <60 ;j++){ // arr[j].x = arr[j].x - 2; // arr[j].y = arr[j].y - 1; arr[j].x = arr[j].x-l/4; arr[j].y = arr[j].y - l/4; this.ctx.drawImage(this.img,arr[j].x,arr[j].y,l,l); l = l - 0.3; if( l < 5) l = 5; } l = 20; arr = []; } if (arr.length==1) { // arr[0].x = this.ctx.drawImage(this.img,arr[0].x1 - l/2,arr[0].y1 - l/2,l,l); console.log(arr[0].x); arr = []; } console.log(arr);} Handwriting.prototype.getXY = function (e) { var x = e.clientX || e.touches[0].clientX; var y = e.clientY || e.touches[0].clientY; // // return { // x : e.clientX - this.canvas.offsetLeft + (document.body.scrollLeft || document.documentElement.scrollLeft), // y : e.clientY - this.canvas.offsetTop + (document.body.scrollTop || document.documentElement.scrollTop) // } return { x : x - this.canvas.offsetLeft + (document.body.scrollLeft || document.documentElement.scrollLeft), y : y - this.canvas.offsetTop + (document.body.scrollTop || document.documentElement.scrollTop) } } Handwriting.prototype.distance = function (a,b) { var x = b.x-a.x , y = b.y-a.y; return Math.sqrt(x*x+y*y); } // $('input').eq(1).val(5);// $('input').eq(2).val(20); var hw = new Handwriting("canvasId"); hw.linePressure = 2.5; hw.smoothness = 100; </script> </body> </html> 本人参考它的源码并在 Android 上实现类似的自定义控件,其效果如下: 源码如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360package com.xxt.xian.view.autograph;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import androidx.annotation.Nullable;import com.xxt.xian.R;import java.util.LinkedList;public class XAutographView extends View { private Paint mPaint; private boolean isTouching; private Bitmap mPenBmp; private RectF mRect; private float smoothness = 80; private float maxL = 50; private float l; private Bitmap mBitmap; private Canvas mCanvas; LinkedList<FPoint> mPoints; LinkedList<Float> mDisList; FPoint prePoint, tempPoint, curPoint; public XAutographView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mRect = new RectF(); mPenBmp = BitmapFactory.decodeResource(getResources(), R.drawable.pen); isTouching = false; mPoints = new LinkedList<>(); mDisList = new LinkedList<>(); prePoint = new FPoint(); tempPoint = new FPoint(); curPoint = new FPoint(); mCanvas = new Canvas(); } @Override protected void onSizeChanged(int w, int h, int oldW, int oldH) { super.onSizeChanged(w, h, oldW, oldH); if (mBitmap != null) mBitmap.recycle(); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mCanvas.setBitmap(mBitmap); maxL = w / 30f; l = maxL; } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isTouching = true; mPoints.clear(); prePoint.set(x, y); mPoints.addFirst(new FPoint(x, y)); return true; case MotionEvent.ACTION_MOVE: drawMoveCanvas(x, y); break; case MotionEvent.ACTION_UP: drawUpCanvas(); break; default:break; } return super.onTouchEvent(event); } private void drawUpCanvas() { isTouching = false; l = maxL; if (mPoints.size() > 200) { for (int j = 0; j < 100; j++) { FPoint p = mPoints.get(j); p.x = p.x - l / 4; p.y = p.y - l / 4; mRect.set(p.x, p.y, p.x+l, p.y+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); l = l - 0.3f;// if( l < maxL / 10) l = maxL / 10; } mPoints.clear(); } if (mPoints.size() == 1) { FPoint p = mPoints.get(0); float x = p.x - l / 2; float y = p.y - l / 2; mRect.set(x, y, x+l, y+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); mPoints.clear(); } } private void drawMoveCanvas(final float x, final float y) { new Thread(new Runnable() { @Override public void run() { if (isTouching) { curPoint.set(x, y); tempPoint.set(prePoint.x, prePoint.y); mDisList.addFirst(getDistance(tempPoint, curPoint)); float dis = 0; for (int n = 0; n < mDisList.size(); n++) { dis += mDisList.get(n); if (dis > smoothness) break; } prePoint.set(curPoint.x, curPoint.y); int len = Math.round(mDisList.get(0) / 2) + 1; for (int i = 0; i < len; i++) { float x1 = tempPoint.x + (curPoint.x - tempPoint.x) / len * i; float y1 = tempPoint.y + (curPoint.y - tempPoint.y) / len * i; x1 = x1 - l/2; y1 = y1 - l/2; mPoints.addFirst(new FPoint(x1, y1)); mRect.set(x1, y1, x1+l, y1+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); l = l - 0.2f; if( l < maxL / 2) l = maxL / 2; } postInvalidate(); } } }).start(); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, 0, 0, mPaint); } private float getDistance(FPoint p1, FPoint p2) { float x = p2.x - p1.x; float y = p2.y - p1.y; return (float) Math.sqrt(x * x + y * y); } class FPoint { float x; float y; FPoint() {} FPoint(float x, float y) { this.x = x; this.y = y; } void set(float x, float y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FPoint point = (FPoint) o; if (x != point.x) return false; if (y != point.y) return false; return true; } @Override public String toString() { return "Point(" + x + ", " + y + ")"; } }}package xian.xiao.tao.ui;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import androidx.annotation.Nullable;import java.util.LinkedList;import xian.xiao.tao.R;public class XAutographView extends View { private Paint mPaint; private boolean isTouching; private Bitmap mPenBmp; private RectF mRect; private float smoothness = 80; private float maxL = 50; private float l; private Bitmap mBitmap; private Canvas mCanvas; LinkedList<XPoint> mPoints; LinkedList<Float> mDisList; XPoint prePoint, tempPoint, curPoint; public XAutographView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mRect = new RectF(); mPenBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.ui_x_autograph_view_nib); isTouching = false; mPoints = new LinkedList<>(); mDisList = new LinkedList<>(); prePoint = new XPoint(); tempPoint = new XPoint(); curPoint = new XPoint(); mCanvas = new Canvas(); } @Override protected void onSizeChanged(int w, int h, int oldW, int oldH) { super.onSizeChanged(w, h, oldW, oldH); if (mBitmap != null) mBitmap.recycle(); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); mCanvas.setBitmap(mBitmap); maxL = w / 30f; l = maxL; } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isTouching = true; mPoints.clear(); prePoint.set(x, y); mPoints.addFirst(new XPoint(x, y)); return true; case MotionEvent.ACTION_MOVE: drawMoveCanvas(x, y); break; case MotionEvent.ACTION_UP: drawUpCanvas(); break; default:break; } return super.onTouchEvent(event); } private void drawMoveCanvas(final float x, final float y) { new Thread(new Runnable() { @Override public void run() { if (isTouching) { curPoint.set(x, y); tempPoint.set(prePoint.x, prePoint.y); mDisList.addFirst(getDistance(tempPoint, curPoint)); float dis = 0; for (int n = 0; n < mDisList.size(); n++) { dis += mDisList.get(n); if (dis > smoothness) break; } prePoint.set(curPoint.x, curPoint.y); int len = Math.round(mDisList.get(0) / 2) + 1; for (int i = 0; i < len; i++) { float x1 = tempPoint.x + (curPoint.x - tempPoint.x) / len * i; float y1 = tempPoint.y + (curPoint.y - tempPoint.y) / len * i; x1 = x1 - l/2; y1 = y1 - l/2; mPoints.addFirst(new XPoint(x1, y1)); mRect.set(x1, y1, x1+l, y1+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); l = l - 0.2f; if( l < maxL / 2) l = maxL / 2; } postInvalidate(); } } }).start(); } private void drawUpCanvas() { isTouching = false; l = maxL; if (mPoints.size() > 200) { for (int j = 0; j < 100; j++) { XPoint p = mPoints.get(j); p.x = p.x - l / 4; p.y = p.y - l / 4; mRect.set(p.x, p.y, p.x+l, p.y+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); l = l - 0.3f; } mPoints.clear(); } if (mPoints.size() == 1) { XPoint p = mPoints.get(0); float x = p.x - l / 2; float y = p.y - l / 2; mRect.set(x, y, x+l, y+l); mCanvas.drawBitmap(mPenBmp, null, mRect, mPaint); mPoints.clear(); } } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, 0, 0, mPaint); } private float getDistance(XPoint p1, XPoint p2) { float x = p2.x - p1.x; float y = p2.y - p1.y; return (float) Math.sqrt(x * x + y * y); } class XPoint { float x; float y; XPoint() {} XPoint(float x, float y) { this.x = x; this.y = y; } void set(float x, float y) { this.x = x; this.y = y; } }} 资源图片: